package org.alis.rpc.proxy;

import lombok.extern.slf4j.Slf4j;
import org.alis.rpc.common.bean.RpcRequest;
import org.alis.rpc.common.util.AlisIdUtils;
import org.alis.rpc.common.util.ServerUtils;
import org.alis.rpc.handler.RpcClientHandler;
import org.alis.rpc.handler.RpcFuture;
import org.alis.rpc.manager.ConnectManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * ObjectProxy desc: 动态代理对象
 *
 * @author luochuan
 * @date 2020/9/30
 */
@Slf4j
public class ObjectProxy<T, P> implements InvocationHandler, RpcService<T, P, SerializableFunction> {

    /**
     * 服务接口版本号
     */
    private String version;
    /**
     * 类对象
     */
    private Class<?> clazz;


    public ObjectProxy(String version, Class<?> clazz) {
        this.clazz = clazz;
        this.version = version;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class == method.getDeclaringClass()) {
            String methodName = method.getName();
            switch (methodName) {
                case "equals":
                    return proxy == args[0];
                case "hashCode":
                    return System.identityHashCode(proxy);
                case "toString":
                    return proxy.getClass().getName() + "@" +
                            Integer.toHexString(System.identityHashCode(proxy)) +
                            ", with InvocationHandler " + this;
                default:
                    throw new IllegalStateException(String.valueOf(method));
            }

        }
        RpcRequest request = new RpcRequest();
        request.setRequestId(AlisIdUtils.getUuid());
        request.setVersion(this.version);
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setParameters(args);
        // debug
        if (log.isDebugEnabled()) {
            log.debug(method.getDeclaringClass().getName());
            log.debug(method.getName());
            for (Class clazz : method.getParameterTypes()) {
                log.debug(clazz.getName());
            }
            for (Object arg : args) {
                log.debug(arg.toString());
            }
        }
        String serverKey = ServerUtils.makeServiceKey(method.getDeclaringClass().getName(), version);
        RpcClientHandler<T> handler = ConnectManager.<T>getInstance().chooseHandler(serverKey);
        RpcFuture<T> rpcFuture = handler.sendRequest(request);
        return rpcFuture.get();

    }

    @SafeVarargs
    @Override
    public final RpcFuture<T> call(String functionName, P... args) throws Exception {
        String serviceKey = ServerUtils.makeServiceKey(this.clazz.getName(), version);
        RpcClientHandler<T> handler =  ConnectManager.<T>getInstance().chooseHandler(serviceKey);
        RpcRequest request = createRequest(this.clazz.getName(), functionName, args);
        return handler.sendRequest(request);
    }

    @SafeVarargs
    @Override
    public final RpcFuture<T> call(SerializableFunction tSerializableFunction, P... args) throws Exception {
        String serviceKey = ServerUtils.makeServiceKey(this.clazz.getName(), version);
        RpcClientHandler<T> handler =  ConnectManager.<T>getInstance().chooseHandler(serviceKey);
        RpcRequest request = createRequest(this.clazz.getName(), tSerializableFunction.getName(), args);
        return handler.sendRequest(request);
    }

    private RpcRequest createRequest(String className, String methodName, Object[] args) {
        RpcRequest request = new RpcRequest();
        request.setRequestId(AlisIdUtils.getUuid());
        request.setClassName(className);
        request.setMethodName(methodName);
        request.setParameters(args);
        request.setVersion(version);
        Class[] parameterTypes = new Class[args.length];
        // Get the right class type
        for (int i = 0; i < args.length; i++) {
            parameterTypes[i] = getClassType(args[i]);
        }
        request.setParameterTypes(parameterTypes);

        // Debug
        if (log.isDebugEnabled()) {
            log.debug(className);
            log.debug(methodName);
            for (Class parameterType : parameterTypes) {
                log.debug(parameterType.getName());
            }
            for (Object arg : args) {
                log.debug(arg.toString());
            }
        }

        return request;
    }


    private Class<?> getClassType(Object obj) {
        return obj.getClass();
    }
}